﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

namespace xBei.Redis.Extension {
    /// <summary>
    /// 
    /// </summary>
    public sealed partial class RedisClient {

        private ConnectionMultiplexer _redis = default!;
        private readonly string configuration;
        private static readonly Dictionary<string, ConnectionMultiplexer> _redisList = new();
        private static RedisClient client = default!;
        /// <summary>
        /// Redis客户端
        /// 基于 StackExchange.Redis
        /// </summary>
        /// <param name="configuration"></param>
        public RedisClient(string configuration) {
            var options = ConfigurationOptions.Parse(configuration);
            DbIndex = options.DefaultDatabase ?? -1;
            if (_redisList.ContainsKey(configuration)) {
                _redis = _redisList[configuration];
            } else {
                //_redis = ConnectionMultiplexer.Connect(configuration);
                //_redisList[configuration] = _redis;
            }
            this.configuration = configuration;
            client = this;
        }
        private RedisClient(ConnectionMultiplexer redis) {
            _redis = redis;
            client = this;
            configuration = redis.Configuration;
        }
        /// <summary>
        /// Redis客户端
        /// 基于 StackExchange.Redis
        /// </summary>
        /// <param name="host"></param>
        /// <param name="port"></param>
        /// <param name="password"></param>
        /// <param name="dbIndex"></param>
        public RedisClient(string host, int port, string password, int dbIndex) {
            var connectionConfig = new ConfigurationOptions {
                EndPoints = { { host, port } },
                Password = password,
                DefaultDatabase = dbIndex,
            };
            var configuration = connectionConfig.ToString(true);
            this.configuration = configuration;
            DbIndex = dbIndex;
            if (_redisList.ContainsKey(configuration)) {
                _redis = _redisList[configuration];
            } else {
                //_redis = ConnectionMultiplexer.Connect(configuration);
                //_redisList[configuration] = _redis;
            }
            client = this;
        }

        /// <summary>
        /// Redis客户端
        /// 基于 StackExchange.Redis
        /// </summary>
        /// <param name="options"></param>
        public RedisClient(IOptions<RedisSettings> options) : this(options.Value.RedisConnectionHost,
                                                                   options.Value.RedisConnectionPort,
                                                                   options.Value.RedisConnectionPassword,
                                                                   options.Value.RedisDb) {
        }

        #region 基础
        /// <summary>
        /// 获取一个Key的RedisClient实例
        /// </summary>
        /// <returns></returns>
        public static RedisClient Current => client;
        /// <summary>
        /// 当前数据库
        /// </summary>
        public int DbIndex { get; private set; } = -1;
        private ISubscriber GetSubscriber(object? asyncState = null) {
            if (_redis == null) {
                _redis = ConnectionMultiplexer.Connect(configuration);
                _redisList[configuration] = _redis;
            }
            return _redis.GetSubscriber(asyncState);
        }
        private async Task<ISubscriber> GetSubscriberAsync(object? asyncState = null) {
            if (_redis == null) {
                _redis = await ConnectionMultiplexer.ConnectAsync(configuration);
                _redisList[configuration] = _redis;
            }
            return _redis.GetSubscriber(asyncState);
        }
        private IDatabase getDatabase() {
            if (_redis == null) {
                _redis = ConnectionMultiplexer.Connect(configuration);
                _redisList[configuration] = _redis;
            }
            return _redis.GetDatabase(DbIndex);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public async Task<IDatabase> GetDatabaseAsync() {
            if (_redis == null) {
                _redis = await ConnectionMultiplexer.ConnectAsync(configuration);
                _redisList[configuration] = _redis;
            }
            return _redis.GetDatabase(DbIndex);
        }
        /// <summary>
        /// 返回IDatabase
        /// </summary>
        public IDatabase Database => getDatabase();
        private string _namespace = "";
        /// <summary>
        /// 设置缓存Key前缀（命名空间）
        /// </summary>
        /// <param name="ns"></param>
        /// <param name="copy"></param>
        /// <returns></returns>
        public RedisClient SetNamespace(string ns, bool copy = false) {
            if (string.IsNullOrWhiteSpace(ns)) {
                if (copy) {
                    return new RedisClient(_redis) {
                        _namespace = "",
                    };
                }
                _namespace = "";
                return this;
            }
            const char sp = ':';
            var nsTmp = ns.Contains($"{sp}")
                            ? ns.Split(new[] { sp }, StringSplitOptions.RemoveEmptyEntries).JoinString(sp) + sp
                            : ns + sp;
            if (copy) {
                return new RedisClient(_redis) {
                    _namespace = nsTmp,
                };
            }
            _namespace = nsTmp;
            return this;
        }
        /// <summary>
        /// 设置数据库
        /// </summary>
        /// <param name="index"></param>
        /// <param name="copy"></param>
        /// <returns></returns>
        public RedisClient SetIndex(int index, bool copy = false) {
            if (copy) {
                return new RedisClient(_redis) {
                    _namespace = this._namespace,
                    DbIndex = index
                };
            }
            DbIndex = index;
            return this;
        }
        private RedisKey GetKey(string key) {
            return $"{_namespace}{key}";
        }
        private RedisKey GetKey(RedisKey key) {
            return $"{_namespace}{key}";
        }
        #endregion

        /// <summary>
        /// 把当前数据库中所有数据（Key）删除
        /// </summary>
        public RedisClient Clear()
            => ClearAsync().Result;
        /// <summary>
        /// 把当前数据库中所有数据（Key）删除
        /// </summary>
        public async Task<RedisClient> ClearAsync() {
            await (await GetDatabaseAsync()).ExecuteAsync("flushdb", null, flags: CommandFlags.FireAndForget);
            return this;
        }

        #region Key
        /// <summary>
        /// Key是否存在
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public bool Exists(string Key) => ExistsAsync(Key).Result;
        /// <summary>
        /// Key是否存在
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public async Task<bool> ExistsAsync(string Key) => await (await GetDatabaseAsync()).KeyExistsAsync(GetKey(Key));
        /// <summary>
        /// 删除Key
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public RedisClient Remove(string Key) => RemoveAsync(Key).Result;
        /// <summary>
        /// 删除Key
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public async Task<RedisClient> RemoveAsync(string Key) {
            await (await GetDatabaseAsync()).KeyDeleteAsync(GetKey(Key));
            return this;
        }
        internal async Task<RedisClient> RemoveAsync(RedisKey Key) {
            await (await GetDatabaseAsync()).KeyDeleteAsync(GetKey(Key));
            return this;
        }
        /// <summary>
        /// 搜索Key（KEYS pattern）
        /// </summary>
        /// <param name="pattern"></param>
        /// <returns></returns>
        public IEnumerable<string> SearchKey(string pattern) => SearchKeyAsync(pattern).Result;
        /// <summary>
        /// 搜索Key（KEYS pattern）
        /// </summary>
        /// <param name="pattern"></param>
        /// <returns></returns>
        public async Task<IEnumerable<string>> SearchKeyAsync(string pattern) {
            return ((string[]?)await getDatabase().ExecuteAsync("KEYS", pattern)) ?? Array.Empty<string>();
        }
        /// <summary>
        /// 搜索Key（SCAN cursor MATCH match COUNT count）
        /// </summary>
        /// <param name="cursor"></param>
        /// <param name="pattern"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public ScanResult<string> ScanKey(long cursor, string pattern, int count)
            => ScanKeyAsync(cursor, pattern, count).Result;
        /// <summary>
        /// 搜索Key（SCAN cursor MATCH match COUNT count）
        /// </summary>
        /// <param name="cursor"></param>
        /// <param name="pattern"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public async Task<ScanResult<string>> ScanKeyAsync(long cursor, string pattern, int count) {
            var r = await ExecuteCursorAsync("SCAN", cursor, "MATCH", pattern, "COUNT", count);
            return new ScanResult<string> {
                Items = r.List.Cast<string>(),
                Limit = count,
                Cursor = r.Cursor,
            };
        }
        /// <summary>
        /// 返回数据类型
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public RedisType GetKeyType(string key) => getDatabase().KeyType(key);
        /// <summary>
        /// 返回数据类型
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<RedisType> GetKeyTypeAsync(string key) => await (await GetDatabaseAsync()).KeyTypeAsync(key);
        #region 获取过期时间
        /// <summary>
        /// TTL
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public TimeSpan? GetExpire(string key) => getDatabase().KeyTimeToLive(key);
        /// <summary>
        /// TTL
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<TimeSpan?> GetExpireAsync(string key) => await (await GetDatabaseAsync()).KeyTimeToLiveAsync(key);
        #endregion
        #region 设置过期时间
        /// <summary>
        /// 设置过期时间
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public RedisClient Expire(string Key, TimeSpan expiry) => ExpireAsync(Key, expiry).Result;
        /// <summary>
        /// 设置过期时间
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public async Task<RedisClient> ExpireAsync(string Key, TimeSpan expiry) {
            await (await GetDatabaseAsync()).KeyExpireAsync(GetKey(Key), expiry, CommandFlags.FireAndForget);
            return this;
        }
        /// <summary>
        /// 设置过期时间
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public RedisClient Expire(string Key, DateTime expiry) => ExpireAsync(Key, expiry).Result;
        /// <summary>
        /// 设置过期时间
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public async Task<RedisClient> ExpireAsync(string Key, DateTime expiry) {
            await (await GetDatabaseAsync()).KeyExpireAsync(GetKey(Key), expiry, CommandFlags.FireAndForget);
            return this;
        }
        /// <summary>
        /// 设置过期时间
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Seconds">
        /// <list type="bullet">
        /// <item>0  - 直接删除</item>
        /// <item>&gt;0 - 设置为N秒以后过期（自动删除）</item>
        /// <item>&lt;0 - 设置为永久不过期</item>
        /// </list>
        /// </param>
        /// <returns></returns>
        public RedisClient Expire(string Key, int Seconds) => ExpireAsync(Key, Seconds).Result;
        /// <summary>
        /// 设置过期时间
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Seconds">
        /// <list type="bullet">
        /// <item>0  - 直接删除</item>
        /// <item>&gt;0 - 设置为N秒以后过期（自动删除）</item>
        /// <item>&lt;0 - 设置为永久不过期</item>
        /// </list>
        /// </param>
        /// <returns></returns>
        public async Task<RedisClient> ExpireAsync(string Key, int Seconds) {
            if (Seconds == 0) return await RemoveAsync(Key);
            if (Seconds < 0)
                await (await GetDatabaseAsync()).KeyPersistAsync(GetKey(Key), CommandFlags.FireAndForget);
            else
                await (await GetDatabaseAsync()).KeyExpireAsync(GetKey(Key), TimeSpan.FromSeconds(Seconds), CommandFlags.FireAndForget);
            return this;
        }
        internal async Task<RedisClient> ExpireAsync(RedisKey Key, int Seconds) {
            if (Seconds == 0) return await RemoveAsync(Key);
            if (Seconds < 0)
                await (await GetDatabaseAsync()).KeyPersistAsync(GetKey(Key), CommandFlags.FireAndForget);
            else
                await (await GetDatabaseAsync()).KeyExpireAsync(GetKey(Key), TimeSpan.FromSeconds(Seconds), CommandFlags.FireAndForget);
            return this;
        }
        #endregion
        #endregion

        #region String
        #region Get
        /// <summary>
        /// 获取值，如果Key不存在则返回 dv
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public RedisValue GetValue(string Key, RedisValue dv) => GetValueAsync(Key, dv).Result;
        /// <summary>
        /// 获取值，如果Key不存在则返回 dv
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<RedisValue> GetValueAsync(string Key, RedisValue dv)
            => await ExistsAsync(Key) ? await (await GetDatabaseAsync()).StringGetAsync(GetKey(Key)) : dv;
        /// <summary>
        /// 获取值，如果Key不存在则返回 dv
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public DateTime GetValue(string Key, DateTime dv) => GetValueAsync(Key, dv).Result;
        /// <summary>
        /// 获取值，如果Key不存在则返回 dv
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<DateTime> GetValueAsync(string Key, DateTime dv)
            => await ExistsAsync(Key) ? new DateTime((long)await (await GetDatabaseAsync()).StringGetAsync(GetKey(Key))) : dv;
        /// <summary>
        /// 获取值，如果Key不存在则返回 dv
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public T? GetObject<T>(string Key, T? dv = null) where T : class, new()
             => GetObjectAsync(Key, dv).Result;
        /// <summary>
        /// 获取值，如果Key不存在则返回 dv
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<T?> GetObjectAsync<T>(string Key, T? dv = null) where T : class, new()
            => await ExistsAsync(Key)
                ? ((string?)await (await GetDatabaseAsync()).StringGetAsync(GetKey(Key))).Deserialize<T>()
                : dv;
        #endregion
        #region Set
        #region 自增自减
        /// <summary>
        /// 自增
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="result"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public RedisClient Increment(string Key, out long result, long amount = 1) {
            result = getDatabase().StringIncrement(GetKey(Key), amount);
            return this;
        }
        /// <summary>
        /// 自增
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public async Task<long> IncrementAsync(string Key, long amount = 1) {
            return await (await GetDatabaseAsync()).StringIncrementAsync(GetKey(Key), amount);
        }
        /// <summary>
        /// 自增（不返回值）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public RedisClient Increment(string Key, long amount = 1) => RedisClientAsync(Key, amount).Result;
        /// <summary>
        /// 自增（不返回值）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public async Task<RedisClient> RedisClientAsync(string Key, long amount = 1) {
            await (await GetDatabaseAsync()).StringIncrementAsync(GetKey(Key), amount, CommandFlags.FireAndForget);
            return this;
        }
        /// <summary>
        /// 自减（不返回值）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public RedisClient Decrement(string Key, long amount = 1) => DecrementAsync(Key, amount).Result;
        /// <summary>
        /// 自减（不返回值）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public async Task<RedisClient> DecrementAsync(string Key, long amount = 1) {
            await (await GetDatabaseAsync()).StringDecrementAsync(GetKey(Key), amount, CommandFlags.FireAndForget);
            return this;
        }
        /// <summary>
        /// 自减
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public long DecrementResult(string Key, long amount = 1) => DecrementResultAsync(Key, amount).Result;
        /// <summary>
        /// 自减
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public async Task<long> DecrementResultAsync(string Key, long amount = 1) {
            return await (await GetDatabaseAsync()).StringDecrementAsync(GetKey(Key), amount);
        }
        #endregion
        /// <summary>
        /// 设置值，如果Key已经存在则会覆盖
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public RedisClient SetValue(string Key, RedisValue Value, int Seconds = 0) => SetValueAsync(Key, Value, Seconds).Result;
        /// <summary>
        /// 设置值，如果Key已经存在则会覆盖
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public async Task<RedisClient> SetValueAsync(string Key, RedisValue Value, int Seconds = 0) {
            if (Seconds > 0)
                await (await GetDatabaseAsync()).StringSetAsync(GetKey(Key), Value, TimeSpan.FromSeconds(Seconds), flags: CommandFlags.FireAndForget);
            else
                await (await GetDatabaseAsync()).StringSetAsync(GetKey(Key), Value, flags: CommandFlags.FireAndForget);
            return this;
        }
        /// <summary>
        /// 设置值，如果Key已经存在则会覆盖
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public RedisClient SetValue(string Key, DateTime Value, int Seconds = 0) => SetValueAsync(Key, Value, Seconds).Result;
        /// <summary>
        /// 设置值，如果Key已经存在则会覆盖
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public async Task<RedisClient> SetValueAsync(string Key, DateTime Value, int Seconds = 0) {
            if (Seconds > 0)
                await (await GetDatabaseAsync()).StringSetAsync(GetKey(Key), Value.Ticks, TimeSpan.FromSeconds(Seconds), flags: CommandFlags.FireAndForget);
            else
                await (await GetDatabaseAsync()).StringSetAsync(GetKey(Key), Value.Ticks, flags: CommandFlags.FireAndForget);
            return this;
        }
        /// <summary>
        /// 设置值，如果Key已经存在则会覆盖
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public RedisClient SetValue<T>(string Key, T Value, int Seconds = 0) where T : class, new()
            => SetValueAsync<T>(Key, Value, Seconds).Result;
        /// <summary>
        /// 设置值，如果Key已经存在则会覆盖
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public async Task<RedisClient> SetValueAsync<T>(string Key, T Value, int Seconds = 0) where T : class, new() {
            if (Seconds > 0)
                await (await GetDatabaseAsync()).StringSetAsync(GetKey(Key), Value.Serialize(), TimeSpan.FromSeconds(Seconds), flags: CommandFlags.FireAndForget);
            else
                await (await GetDatabaseAsync()).StringSetAsync(GetKey(Key), Value.Serialize(), flags: CommandFlags.FireAndForget);
            return this;
        }
        #endregion
        #endregion

        #region Extension
        /// <summary>
        /// CONFIG GET databases
        /// </summary>
        /// <returns></returns>
        public int GetDatabases() {
            var r = getDatabase().Execute("config", "get", "databases");
            var r1 = (StackExchange.Redis.RedisResult[]?)r;
            return r1 == null ? 0 : (int)r1[1];
        }
        /// <summary>
        /// CONFIG GET databases
        /// </summary>
        /// <returns></returns>
        public async Task<int> GetDatabasesAsync() {
            var r = await getDatabase().ExecuteAsync("config", "get", "databases");
            var r1 = (StackExchange.Redis.RedisResult[]?)r;
            return r1 == null ? 0 : (int)r1[1];
        }
        /// <summary>
        /// 返回当前数据库的 key 的数量。
        /// </summary>
        /// <returns></returns>
        public long GetDatabaseSize() => ExecuteLong("DBSIZE");
        /// <summary>
        /// 返回当前数据库的 key 的数量。
        /// </summary>
        /// <returns></returns>
        public async Task<long> GetDatabaseSizeAsync() => await ExecuteLongAsync("DBSIZE");
        #endregion

        #region Sub Class
        /// <summary>
        /// 分页数据集
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class PageSet<T> {
            /// <summary>
            /// 数据列表
            /// </summary>
            public IEnumerable<T> Items { get; internal set; } = default!;
            /// <summary>
            /// 当前页
            /// </summary>
            public int Page { get; internal set; }
            /// <summary>
            /// 记录总数
            /// </summary>
            public long Total { get; internal set; }
            /// <summary>
            /// 分页大小
            /// </summary>
            public int Limit { get; internal set; }
            /// <summary>
            /// 总页数
            /// </summary>
            public int PageCount { get; internal set; }
        }
        /// <summary>
        /// 扫描结果数据集
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class ScanResult<T> {
            /// <summary>
            /// 数据列表
            /// </summary>
            public IEnumerable<T> Items { get; internal set; } = default!;
            /// <summary>
            /// 页面大小
            /// </summary>
            public int Limit { get; internal set; }
            /// <summary>
            /// 游标
            /// </summary>
            public long Cursor { get; internal set; }
            /// <summary>
            /// 时候有更多数据
            /// </summary>
            public bool MayBeHasMore => Items.Count() >= Limit;
        }
        /// <summary>
        /// 搜索Hash结果数据集
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class HashScanResult<T> {
            /// <summary>
            /// 
            /// </summary>
            public IReadOnlyDictionary<string, T> Items { get; internal set; } = default!;
            /// <summary>
            /// 页面大小
            /// </summary>
            public int Limit { get; internal set; }
            /// <summary>
            /// 当前页
            /// </summary>
            public int Page { get; internal set; }
            /// <summary>
            /// 时候还有更多少数据
            /// </summary>
            public bool MayBeHasMore => Items.Count() >= Limit;
            /// <summary>
            /// Redis Key
            /// </summary>
            public string Key { get; internal set; } = default!;
        }
        #endregion

        /// <summary>
        /// 设置Json
        /// </summary>
        /// <param name="setup"></param>
        /// <returns></returns>
        public static JsonSerializerOptions JsonConfig(Func<JsonSerializerOptions, JsonSerializerOptions> setup) {
            JsonSerializerOptions = setup.Invoke(JsonSerializerOptions);
            return JsonSerializerOptions;
        }

        internal static JsonSerializerOptions JsonSerializerOptions = new() {
            //Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
            WriteIndented = false,
        };
    }
}
